package com.h3bpm.web.utils;

import com.microsoft.schemas.vml.CTTextPath;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.namespace.QName;
import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 *  word 标识数据 (${XX}) 替换,
 */
public class DocxUnitl {
    private static final Logger logger = LoggerFactory.getLogger(DocxUnitl.class);

    /**
     * 锚点变量替换,生成水印,添加二维码
     * @param content docx
     * @param matrix 二维码
     * @param params 锚点变量
     * @return
     * @throws Exception
     */

    public byte[] generateFinalContract(byte[] content,byte[] matrix, Map<String, Object> params) throws Exception {
        InputStream inputStream = new ByteArrayInputStream(content);
        byte[] bytes = null;

        //这里用BufferedInputStream再包装一层，可解决：mark/reset not supported问题
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        //转成二进制
        ByteArrayOutputStream bout = null;

        if (versionIs2007(bis)) {
            CustomXWPFDocument docx = new CustomXWPFDocument(bis);
            //替换段落里面的变量
            replaceInDocx(docx, params);

            bout = new ByteArrayOutputStream();
            docx.write(bout);
            bytes = bout.toByteArray();

            close(bis);
            close(inputStream);
            docx.close();
        }else{
            HWPFDocument doc = new HWPFDocument(bis);
            replaceHWPF(doc,params);
            bout = new ByteArrayOutputStream();
            doc.write(bout);
            bytes = bout.toByteArray();

            close(bis);
            close(inputStream);
            doc.close();
        }


        //设置水印
//        setWatermarkStyle(docx,"已 审 批");

        //添加二维码
//        if (matrix != null) {
//            InputStream matrixIs = new ByteArrayInputStream(matrix);
//            setPictur(docx,matrixIs,XWPFDocument.PICTURE_TYPE_JPEG);
////            close(matrixIs);
//        }

        return bytes;
    }

    /**
     *  替换doc 2003 版本的
     *
     * @param doc
     * @param map
     */
    private void replaceHWPF(HWPFDocument doc, Map<String, Object> map){
        //替换读取到的word模板内容的指定字段
        Range range = doc.getRange();
        String str = "${%s}";
        for (Map.Entry<String,Object> entry:map.entrySet()) {
            range.replaceText(String.format(str,  entry.getKey()) , entry.getValue().toString());
        }
    }

    // 锚点变量替换,docx可完全处理，doc会失败
    public byte[] replaceInDocx(byte[] content,Map<String, Object> params) throws Exception {
        InputStream inputStream = new ByteArrayInputStream(content);
        byte[] bytes = null;

        //这里用BufferedInputStream再包装一层，可解决：mark/reset not supported问题
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        //转成二进制
        ByteArrayOutputStream bout = null;

        if (versionIs2007(bis)) {
            CustomXWPFDocument docx = new CustomXWPFDocument(bis);
            //替换段落里面的变量
            replaceInDocx(docx, params);

            bout = new ByteArrayOutputStream();
            docx.write(bout);
            bytes = bout.toByteArray();

            close(bis);
            close(inputStream);
            docx.close();
        }else{
            HWPFDocument doc = new HWPFDocument(bis);
            replaceHWPF(doc,params);
            bout = new ByteArrayOutputStream();
            doc.write(bout);
            bytes = bout.toByteArray();

            close(bis);
            close(inputStream);
            doc.close();
        }

        return bytes;
    }


    public  boolean versionIs2007(InputStream bis) {
        boolean flag = false;
        try {

            if(POIFSFileSystem.hasPOIFSHeader(bis)) {
                logger.info("解析当前 版本为 2007 以下");
//                HWPFDocument hwpfDocument = new HWPFDocument(bis);

            }else  if(POIXMLDocument.hasOOXMLHeader(bis)) {
                logger.info("解析当前 版本为 2007及以上");
//                XWPFDocument xwpfDocument = new XWPFDocument(bis);
                flag = true;
            }else{
                logger.info("解析当前 workbook 获取失败");
            }

        }  catch (Exception e) {
            logger.info("解析当前 workbook 异常:{}",e.getMessage());
            e.printStackTrace();

        }
        return  flag;
    }



    /**
     * 替换docx里面的变量
     * @param docx 要替换的文档
     * @param params 参数
     */
    private void replaceInDocx(XWPFDocument docx, Map<String, Object> params) {
        if (params != null) {
            replaceInPara(docx, params);
            replaceInTable(docx, params);
        }
    }




    /**
     * 替换段落里面的变量
     * @param docx 要替换的文档
     * @param params 参数
     */
    private void replaceInPara(XWPFDocument docx, Map<String, Object> params) {
        Iterator<XWPFParagraph> iterator = docx.getParagraphsIterator();
        XWPFParagraph para;

        while (iterator.hasNext()) {
            para = iterator.next();
//            CTPPr pr = para.getCTP().getPPr();
            this.replaceInPara(para, params);
        }
    }



    /**
     * 替换段落里面的变量
     * @param para 要替换的段落
     * @param params 参数
     */
    private void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
        List<XWPFRun> runs;
        Matcher matcher = matcher(para.getParagraphText());
        if (matcher.find() ) {
            runs = para.getRuns();

            for (int i=0; i<runs.size(); i++) {

                XWPFRun run = runs.get(i);
                String runText = run.toString();
                // 不是以 $ 开始的
                if (runText.startsWith("$")){
                    // 不是以 ${ 开始的判断 后一位是否是 以 { 开始的
                    if (!runText.startsWith("${") ){
                        // 最后以为就返回
                        if (i+1 == runs.size()){
                            return;
                        }
                        // 不是以{开始的
                        if (!runs.get(i+1).toString().startsWith("{")){
                            return;
                        }
                    }
                    // 第一个字符不是以 } 结尾的，循环向后找
                    if (!runText.endsWith("}")){
                        int  m = 0,n=0;
                        for (m = i+1; m<runs.size(); m++){
                            run = runs.get(m);
                            runText = run.toString();

                            if (runText.endsWith("}")){
                                n=m;
                                break;
                            }
                        }
                        //找到变量
                        if (n != 0) {
                            StringBuilder sb = new StringBuilder();
                            for (int j = i; j <= n; j++) {
                                run = runs.get(j);
                                sb.append(run.toString());
                                run.setText("", 0);
                            }
                            run.setText(sb.toString(),0);
                            i=n;
                        }

                    }
                    runText = run.toString();
                    matcher = this.matcher(runText);
                    if (matcher.find() && params.containsKey(matcher.group(1))) {
                        runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                        run.setText(runText,0);
                    }

                }
            }
        }
    }





    /**
     * 替换表格里面的变量
     * @param doc 要替换的文档
     * @param params 参数
     */
    private void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        this.replaceInPara(para, params);
                    }
                }
            }
        }
    }




    /**
     * 正则匹配字符串
     * @param str
     * @return
     */
    private Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 关闭输入流
     * @param is
     */
    private void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     * @param os
     */
    private void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     *  添加图片
     * @param doc
     * @param inputStream
     * @param pictureType
     * @throws Exception
     */
    private void setPictur(CustomXWPFDocument doc,InputStream inputStream,int pictureType) throws  Exception {
        XWPFParagraph paragraph = doc.createParagraph();
        XWPFRun run = paragraph.createRun();

        run.setText("印章管理员扫描二维码审核:");
        run.addBreak();

        CTInline ctinline=run.getCTR().addNewDrawing().addNewInline();//设置段落行
        String id = doc.addPictureData(inputStream, pictureType);//添加图片数据
        int id2=doc.getAllPackagePictures().size()+1;
        doc.createPic(id,id2, 235, 235,ctinline);//添加图片
        close(inputStream);
        logger.info("set picture");

    }



    /**
     *  设置 水印 颜色#d8d8d8,旋转315°
     * @param
     */
    private void setWatermarkStyle(XWPFDocument doc,String text ) {
        XWPFHeaderFooterPolicy headerFooterPolicy = doc.getHeaderFooterPolicy();
        if (headerFooterPolicy == null) headerFooterPolicy = doc.createHeaderFooterPolicy();

        headerFooterPolicy.createWatermark("财务已审批");
        XWPFHeader[] headers = new XWPFHeader[2];
        //默认页
        headers[0] = headerFooterPolicy.getDefaultHeader();
        //第一页
        headers[1] = headerFooterPolicy.getFirstPageHeader();

        XWPFParagraph  paragraph;
        for (int i = 0; i < headers.length; i++) {
            paragraph = headers[i].getParagraphArray(0);

            XmlObject[] xmlObjs = paragraph.getCTP().getRArray(0).getPictArray(0).selectChildren(
                    new QName("urn:schemas-microsoft-com:vml", "shape"));


            if (xmlObjs.length > 0) {
                com.microsoft.schemas.vml.CTShape ctshape = (com.microsoft.schemas.vml.CTShape)xmlObjs[0];
                // set fill color
                ctshape.setFillcolor("#d8d8d8");
                // set rotation
                ctshape.setStyle(ctshape.getStyle() + ";rotation:315;");
                //System.out.println(ctshape);

                CTTextPath textpathArray = ctshape.getTextpathArray(0);

            }
        }


    }





    class CustomXWPFDocument extends XWPFDocument {

        CustomXWPFDocument(InputStream is) throws IOException {
            super(is);
        }


        public void createPic(String blipId, int id, int width, int height, CTInline inline) {
            final int EMU = 9525;
            width *= EMU;
            height *= EMU;
            //String blipId = getAllPictures().get(id).getPackageRelationship().getId();


            //CTInline inline = createParagraph().createRun().getCTR().addNewDrawing().addNewInline();


            String picXml = "" +
                    "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">" +
                    "   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
                    "      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">" +
                    " <pic:nvPicPr>" +
                    "    <pic:cNvPr id=\"" + id + "\" name=\"Generated\"/>" +
                    "    <pic:cNvPicPr/>" +
                    " </pic:nvPicPr>" +
                    " <pic:blipFill>" +
                    "    <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +
                    "    <a:stretch>" +
                    "       <a:fillRect/>" +
                    "    </a:stretch>" +
                    " </pic:blipFill>" +
                    " <pic:spPr>" +
                    "    <a:xfrm>" +
                    "       <a:off x=\"0\" y=\"0\"/>" +
                    "       <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" +
                    "    </a:xfrm>" +
                    "    <a:prstGeom prst=\"rect\">" +
                    "       <a:avLst/>" +
                    "    </a:prstGeom>" +
                    " </pic:spPr>" +
                    "      </pic:pic>" +
                    "   </a:graphicData>" +
                    "</a:graphic>";


            //CTGraphicalObjectData graphicData = inline.addNewGraphic().addNewGraphicData();
            XmlToken xmlToken = null;
            try {
                xmlToken = XmlToken.Factory.parse(picXml);
            } catch (XmlException xe) {
                xe.printStackTrace();
            }
            inline.set(xmlToken);
            //graphicData.set(xmlToken);


            inline.setDistT(0);
            inline.setDistB(0);
            inline.setDistL(0);
            inline.setDistR(0);


            CTPositiveSize2D extent = inline.addNewExtent();
            extent.setCx(width);
            extent.setCy(height);


            CTNonVisualDrawingProps docPr = inline.addNewDocPr();
            docPr.setId(id);
            docPr.setName("Picture " + id);
            docPr.setDescr("Generated");
        }
    }


}